﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spanner;

namespace ToDoMVC
{
    internal class ToDoApp : UI
    {
        internal static void WriteToDoApp(string outputPath = @"E:\tmp\Spanner\Demo")
        {
            var initActs = new List<Act> { };

            var toDoAppID = HtmlID("todoapp");
            var headerID = HtmlID("header");
            var newToDoID = HtmlID("new-todo");
            var mainID = HtmlID("main");
            var toggleAllID = HtmlID("toggle-all");
            var toDoListID = HtmlID("todo-list");
            var footerID = HtmlID("footer");
            var toDoCountID = HtmlID("todo-count");
            var filtersID = HtmlID("filters");
            var clearCompletedID = HtmlID("clear-completed");
            var infoID = HtmlID("info");
            var idCounter = Var<int>("idCounter", 0);
            var newToDoTitle = ObsVar<string>("newToDo", "");
            var toDos = ObsVar("toDos", Array<ToDo>());
            var allCompleted = ComputedVar<bool>("allCompleted",
                Every(toDos, Fn<ToDo, bool>(x => x.Completed()))
            );
            var allCompletedCheckbox = ObsVar<bool>("allCompletedCheckbox", false);
            var toggling = Var<bool>("toggling", false);
            var toggleAllCompletedCheckbox = Procedure("toggleAllCompletedCheckbox",
                Local(!allCompletedCheckbox, x => Do(
                    Set(allCompletedCheckbox, x),
                    ForEach(toDos, Proc<ToDo>(td => Set(td.Completed(), x)))
                ))
            );
            var filterOutCompletedStatus = ObsVar("filterOutIfCompletedStatus", Undefined<bool>());
            var filteredToDos = ComputedVar("filteredToDos",
                Filter(toDos, Fn<ToDo, bool>(x => x.Completed() != filterOutCompletedStatus))
            );
            var currentlyEditing = ObsVar<ToDo>("currentlyEditing", Undefined<ToDo>());
            var remainingCount = ComputedVar<int>("remainingCount",
                // Inefficient, but clear.
                Length(Filter(toDos, Fn<ToDo, bool>(x => !x.Completed())))
            );
            var completedCount = ComputedVar<int>("completedCount", Length(toDos) - remainingCount);

            // Wrap an action such that it will only fire if the enter key has been pressed.
            Func<Expr<Fn<Act>>, Expr<Fn<DummyType, KeyEvent, bool>>> ifEnterKeyThen = a =>
                Fn<DummyType, KeyEvent, bool>((dummy, e) =>
                    Cond<bool>(e.keyCode() == 13, ActExpr<bool>(Call<Act>(a), false), true)
                );

            // When the user adds a new to-do item, we first check the edited one has some content.
            // If so, we "persist" it (XXX TODO!) and create a fresh "new" to-do item and add it to
            // the list of to-do items.
            var addNewToDo = Procedure("addNewToDo", Do(
                // Trim the new-title observable.
                Set(newToDoTitle, Trim(newToDoTitle)),
                // Ignore it unless its non-empty.
                IfThen(newToDoTitle != "", Do (
                    // Create a new ToDo item with a new ID.
                    Local<ToDo>(new ToDo(), x => Do(
                        Set(x.ID(), idCounter),
                        Set(idCounter, idCounter + 1),
                        Set(x.Title(), NewObsVar(newToDoTitle)),
                        Set(x.Completed(), NewObsVar<bool>(false)),
                        Push(toDos, x),
                        // Clear the new-title observable.
                        Set(newToDoTitle, "")
                    ))
                ))
            ));

            var removeToDo = Procedure<ToDo>("removeToDo", x =>
                Set(toDos, Filter(toDos, Fn<ToDo, bool>(y => x != y)))
            );

            var stopEditingToDo = Procedure("stopEditingToDo", Set(currentlyEditing, Undefined<ToDo>()));

            var removeCompletedToDos = Procedure("removeCompletedToDos",
                Set(toDos, Filter(toDos, Fn<ToDo, bool>(y => !y.Completed())))
            );

            // Set the "all completed" checkbox depending on changes in the
            // individual to-do item checkboxes.
            initActs.Add(Do(Subscribe(allCompleted, Proc<bool>(x =>
                IfThen(!toggling, Do(
                    Set(toggling, true),
                    Set(allCompletedCheckbox, x),
                    Set(toggling, false)
                ))
            ))));

            // Set the individual to-do item checkboxes depending on changes in the
            // "all completed" checkbox.
            initActs.Add(Do(Subscribe(allCompletedCheckbox, Proc<bool>(x =>
                IfThen(!toggling, Do(
                    Set(toggling, true),
                    ForEach(toDos, Proc<ToDo>(td => Set(td.Completed(), x))),
                    Set(toggling, false)
                ))
            ))));

            // At start-of-day, "route" to the correct filter settings.
            initActs.Add(Local(JSExpr<string>("window.location.hash"), hash =>
                IfCase(
                    When(hash == "#/completed", Set(filterOutCompletedStatus, false)),
                    When(hash == "#/active", Set(filterOutCompletedStatus, true)),
                    When(hash == "#/all", Set(filterOutCompletedStatus, Undefined<bool>()))
                )
            ));

            var bodyContent = Group(

                Section().WithID(toDoAppID).WithChildren(

                    Header().WithID(headerID).WithChildren(

                        H1("todos"),

                        Input().WithID(newToDoID)
                               .KoValue(newToDoTitle)
                               .KoValueUpdate(KoValueUpdateOn.AfterKeyDown)
                               .KoEvent(DomEventHandler(DomEventName.KeyUp, ifEnterKeyThen(addNewToDo)))
                               .WithAttr("autofocus")
                               .WithAttr("placeholder", "What needs to be done?")

                    ),

                    Section().WithID(mainID)
                             .KoVisible(Length(toDos) != 0)
                             .WithChildren(

                        Input().WithID(toggleAllID)
                               .WithAttr("type", "checkbox")
                               .KoChecked(allCompletedCheckbox),

                        Label().WithAttr("for", toggleAllID)
                               .WithChildren("Mark all as complete"),

                        UL().WithID(toDoListID)
                            .KoForEach(filteredToDos, x =>

                                LI().KoCss(CssClassCondition("completed", x.Completed()),
                                           CssClassCondition("editing", currentlyEditing == x))
                                    .KoEvent(DomEventHandler(DomEventName.KeyDown, ifEnterKeyThen(stopEditingToDo)))
                                    .WithChildren(

                                        Div().WithClass("view").WithChildren(

                                            Input().WithClass("toggle")
                                                   .KoChecked(x.Completed())
                                                   .WithAttr("type", "checkbox"),

                                            Label().KoText(x.Title())
                                                   .KoEvent(DomEventHandler(DomEventName.Click, Set(currentlyEditing, x))),

                                            Button().WithClass("destroy")
                                                   .KoClick(Call(removeToDo, x))

                                        ),

                                        Input().WithClass("edit")
                                               .KoValue(x.Title())
                                               .KoValueUpdate(KoValueUpdateOn.AfterKeyDown)
                                               .KoEvent(DomEventHandler(DomEventName.Blur, stopEditingToDo))
                                                // XXX selectAndFocus: x.editing(),

                                     )

                             )

                    ),

                    Footer().WithID(footerID)
                            .KoVisible(Length(toDos) != 0)
                            .WithChildren(

                        Span().WithID(toDoCountID).WithChildren(

                            Strong(Show(remainingCount)),
                            Show(Cond<string>(remainingCount == 1, " item ", " items ")),
                            "left"

                        ),

                        UL().WithID(filtersID).WithChildren(

                            LI(A("All")
                                .WithAttr("href", "#/all")
                                .KoCss(CssClassCondition("selected", filterOutCompletedStatus == Undefined<bool>()))
                                .KoClick(Set(filterOutCompletedStatus, Undefined<bool>()))
                            ),
                            LI(A("Active")
                                .WithAttr("href", "#/active")
                                .KoCss(CssClassCondition("selected", filterOutCompletedStatus == true))
                                .KoClick(Set(filterOutCompletedStatus, true))
                            ),
                            LI(A("Completed")
                                .WithAttr("href", "#/completed")
                                .KoCss(CssClassCondition("selected", filterOutCompletedStatus == false))
                                .KoClick(Set(filterOutCompletedStatus, false))
                            )

                        ),

                        Button().WithID(clearCompletedID)
                                .KoVisible(completedCount != 0)
                                .KoClick(removeCompletedToDos)
                                .WithChildren(

                            "Clear completed (", Show(completedCount), ")"

                        )

                    )

                ),

                Footer().WithID(infoID).WithChildren(
                    P("Double-click to edit a todo"),
                    P(  A("Spanner").WithAttr("href", "https://spanner.codeplex.com/"),
                        " version written by ",
                        A("Ralph Becket").WithAttr("href", "http://ralphbecket.blogspot.com")
                    ),
                    P("Part of ", A("TodoMVC").WithAttr("href", "http://todomvc.com"))
                )

            );

            var headerContent = Group(
                Meta().WithAttr("charset", "utf-8"),
                Meta().WithAttr("content", "IE=edge,chrome=1")
                      .WithAttr("http-equiv", "X-UA-Compatible"),
                Link().WithAttr("rel", "stylesheet")
                      .WithAttr("href", "../css/base.css")
            );

            var html = StandardSpannerHtmlPage("ToDoMVC - Spanner", headerContent, bodyContent);

            var webPage = WebPage("ToDoMVC", html, Do(initActs));

            var ctxt = new Context(outputPath, singleFile: true);

            ctxt.WriteWebPage(webPage);

            using (var sw = new StreamWriter(Path.Combine(outputPath, "css", "base.css")))
                sw.Write(ToDoMVC.Properties.Resources.ToDoMVCBaseCss);
        }
    }
}